﻿using System.Reflection;
using System.Xml;

namespace SelectivelyLoadingObjects
{
    /// <summary>
    /// 该类读取XML并可以让外部选择性地加载指定类对象
    /// </summary>
    internal class XMLClassLoader
    {
        /// <summary>
        /// 对象池
        /// </summary>
        private static Dictionary<string, object> objects = new Dictionary<string, object>();
        /// <summary>
        /// 所有对象初始化时需要的参数（即把XML文件转译后把数据存到该数据结构中）
        /// </summary>
        private static List<Argument> arguments = new List<Argument>();

        public static void LoadXML(string xmlName)
        {
            XmlDocument doc = new XmlDocument();
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreComments = true;
            try
            {
                XmlReader reader = XmlReader.Create(xmlName, settings);
                doc.Load(reader);
                XmlNode classesNode = doc.SelectSingleNode("classes")!;
                XmlNodeList classes = classesNode.ChildNodes;

                foreach (XmlNode classNode in classes)
                {
                    XmlElement classElem = (XmlElement)classNode;
                    string className = classElem.GetAttribute("name").Trim();

                    XmlNode? constructorNode = classNode.SelectSingleNode("constructor");
                    if (constructorNode != null)
                    {
                        foreach (XmlNode node in constructorNode.ChildNodes)
                        {
                            Argument argument = new Argument()
                            {
                                ClassName = className,
                                Type = "Contruct"
                            };
                            XmlElement cstparm = (XmlElement)node;
                            argument.ParamType = cstparm.GetAttribute("type");
                            argument.ParamValue = cstparm.GetAttribute("value");
                            argument.PrarmIndex = cstparm.GetAttribute("index");
                            argument.Ref = cstparm.GetAttribute("ref");
                            arguments.Add(argument);
                        }
                    }


                    XmlNode? propsNode = classNode.SelectSingleNode("properties");
                    if (propsNode != null)
                    {
                        foreach (XmlNode propNode in propsNode.ChildNodes)
                        {
                            Argument argument = new Argument()
                            {
                                ClassName = className,
                                Type = "Prop"
                            };
                            XmlElement prop = (XmlElement)propNode;
                            argument.ParamType = prop.GetAttribute("name").Trim();
                            argument.ParamValue = prop.GetAttribute("value").Trim();
                            arguments.Add(argument);
                        }
                    }

                    XmlElement? refNode = (XmlElement?)classNode.SelectSingleNode("objectref");
                    if (refNode != null)
                    {
                        Argument argument = new Argument()
                        {
                            ClassName = className,
                            MyName = refNode.InnerText.Trim()
                        };
                        arguments.Add(argument);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                throw;
            }
        }

        public static T? GetInstance<T>(string objectRef) where T : class
        {
            T? res = default(T?);
            object? obj = GetOrCreate(objectRef);
            if (obj != null)
            {
                res = obj as T;
            }
            return res;
        }
        /// <summary>
        /// 寻找构造实例依赖链，这条依赖链实际上取决于用户是怎么写XML的
        /// 如果用户给一个类写了objectref节点，那么依赖链会找到它并生成；如果用户没写，将不会生成对象
        /// 不需要每次创建对象时都调用一次依赖链，最好是当对象池里找不到时再通过找到依赖链来生成对象
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static Stack<string> FindConstructDependencyChains(string _ref)
        {
            //利用层序遍历思想遍历构造_ref时需要依赖的所有自定义类实例，再使用栈来保存所有的实例的ref
            Queue<string> queue = new Queue<string>();
            Stack<string> res = new Stack<string>();  //保存所有的ref

            queue.Enqueue(_ref);
            while (queue.Count > 0)
            {
                string curRef = queue.Dequeue();
                res.Push(curRef);
                //找这个类构造时需要的自定义类类型的参数（也就是ref不为空的Argument）
                Argument thisArg = arguments.FirstOrDefault(arg => arg.MyName == curRef);
                if (!thisArg.Equals(default(Argument)))
                {
                    var refArgus = arguments.Where(arg => arg.ClassName == thisArg.ClassName &&
                        arg.Type == "Contruct" && !string.IsNullOrEmpty(arg.Ref));
                    if (refArgus.Count() > 0)
                    {
                        foreach (var refArgu in refArgus)
                        {
                            queue.Enqueue(refArgu.Ref);
                        }
                    }
                }
            }


            return res;
        }
        /// <summary>
        /// 获取或创建指定实例
        /// </summary>
        /// <param name="_ref"></param>
        /// <returns></returns>
        private static object? GetOrCreate(string _ref)
        {

            if (objects.ContainsKey(_ref))
            {
                return objects[_ref];
            }

            object? res = default(Object);

            try
            {
                var thisArgu = arguments.FirstOrDefault(arg => arg.MyName == _ref);
                if (thisArgu.Equals(default(Argument)))
                {
                    return res;
                }
                string className = thisArgu.ClassName;
                Type t = Type.GetType(className)!;
                Assembly asm = t.Assembly;
                var sctArgs = arguments.Where(arg => arg.ClassName == className &&
                arg.Type == "Contruct");
                object[] argsArray = new object[sctArgs.Count()];
                //解析出所有参数并加入到object列表
                //（demo程序所以仅考虑所有参数都是正常输入且不漏输attribute或value的情况）
                foreach (Argument argu in sctArgs)
                {
                    int index = int.Parse(argu.PrarmIndex);
                    if (!string.IsNullOrEmpty(argu.Ref))      //如果当前参数有ref，先处理ref
                    {
                        if (objects.ContainsKey(argu.Ref))
                        {
                            object obj = objects[argu.Ref];
                            if (obj.GetType().FullName == argu.ParamType)  //如果当前对象池中存在该ref对象，并且与xml中声明的形参类型相同
                            {
                                argsArray[index] = obj;
                            }
                            else
                            {
                                throw new Exception($"代号为{argu.Ref}的实例与声明类型{argu.Type}不匹配");
                            }
                        }
                        else
                        {
                            //对象池中不存在的话，就获取构造链，根据构造链递归构造指定的实例
                            var stack = FindConstructDependencyChains(argu.Ref);
                            while (stack.Count > 0)
                            {
                                string curRef = stack.Pop();
                                res = GetOrCreate(curRef);
                            }
                            argsArray[index] = res;
                        }
                    }
                    else
                    {
                        Type _t = Type.GetType(argu.ParamType)!;
                        var paramVal = ConvertTo(_t, argu.ParamValue)!;   //构造指定类型的实参
                        argsArray[index] = paramVal;
                    }
                }
                //用反射调用构造函数创建对象
                res = asm.CreateInstance(className!, true,
                    BindingFlags.Default, null, argsArray, null, null);
                //加入到字典
                objects[_ref] = res!;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                throw;
            }

            return res;
        }

        public static void DestroyInstance(string objectRef)
        {
            if (objects.ContainsKey(objectRef))
            {
                var obj = objects[objectRef];
                objects.Remove(objectRef);
                obj = null;
            }
        }

        public static void DestroyAll()
        {
            objects.Clear();
        }

        private static object? ConvertTo(Type type, string value)
        {
            object? res = default(object?);
            if (type.Name == "String")
            {
                return value;
            }
            if (type.IsValueType)
            {
                if (type.Name == "Int16" || type.Name == "Int32" || type.Name == "Int64" || /*type.Name == "Int128" || 暂不支持.NET7以上 */
                type.Name == "Boolean" || type.Name == "Byte" || type.Name == "Char" ||
                type.Name == "Decimal" || type.Name == "DateTime" || type.Name == "Double" ||
                type.Name == "Single")
                {
                    var methodInfo = type.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public,
                        null, CallingConventions.Any, new Type[] { typeof(string) }, null);
                    try
                    {
                        res = methodInfo?.Invoke(null, new object[] { value });
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }
            }
            return res;
        }
    }

    struct Argument
    {
        /// <summary>
        /// 属于哪个类，完整类名
        /// </summary>
        internal string ClassName;
        /// <summary>
        /// 属于哪一块的参数。构造函数的参数为"Contruct"，属性为"Prop"
        /// </summary>
        internal string Type;
        /// <summary>
        /// 如果type是构造函数，该字段表示参数类型，需要写入完整类型名，如"System.Int32"。
        /// 如果type是属性，该字段表示属性名
        /// </summary>
        internal string ParamType;
        /// <summary>
        /// 参数值，用字符串表示
        /// </summary>
        internal string ParamValue;
        /// <summary>
        /// 参数的索引位置
        /// </summary>
        internal string PrarmIndex;
        /// <summary>
        /// 如果参数是一个自定义类，该字段表示自定义类实例的一个引用名
        /// </summary>
        internal string Ref;
        /// <summary>
        /// 本实例引用名
        /// </summary>
        internal string MyName;
    }


}
